#include "common.h"

#include "utils/systime.h"

#include "Vsim_cache.h"
#include "Vsim_cache___024root.h"
#include "verilated_vcd_c.h"
#define TRACE 1
#define MEM_SIZE 0x100000
#define CACHE_SIZE (4*1024)
#undef WAVE_MAX_CNT
#define WAVE_MAX_CNT 10000
void simcycle(uint32_t n);
void reset();
void dump_wave();
void init_mem();

uint64_t mem[MEM_SIZE / sizeof(uint64_t)] = {0};
Vsim_cache *top;
VerilatedVcdC *tfp;
static uint32_t total_cycles = 0;
#undef SIM_MAX_CYCLES
#define SIM_MAX_CYCLES (MEM_SIZE*16)
uint64_t hit[2];
uint64_t total_access;
typedef struct 
{
    uint64_t addr;
    uint8_t size;
    uint64_t data;
} Load_OP;

uint64_t load(uint64_t addr,uint8_t size)
{
    // while (top->rbusy == 1)
    //     simcycle(1);
    top->ren = 0;
    while (top->rbusy == 1)
    {
        simcycle(1);
    }
    
    top->raddr = addr;
    top->ren = 1;
    top->rsize = size;
    top->eval();
    hit[0] += top->rootp->sim_cache__DOT__inst_cache__DOT__rhit0;
    hit[1] += top->rootp->sim_cache__DOT__inst_cache__DOT__rhit1;
    total_access ++;
    simcycle(1);
    top->ren = 0;
    while (top->rdata_valid == 0) 
        simcycle(1);
    uint64_t mask;
    switch (size)
    {
    case 1: mask = 0xffull; break;
    case 2: mask = 0xffffull; break;
    case 4: mask = 0xffffffffull; break;
    case 8: mask = 0xffffffffffffffffull; break;
    
    default: assert(0); break;
    }
    return top->rdata&mask;
}
void store(uint64_t addr, uint64_t data, uint64_t size)
{
    top->wen = 0;
    while (top->wbusy == 1)
        simcycle(1);
    
    top->wen = 1;
    top->waddr = addr;
    top->wdata = data;
    top->wsize = size;
    simcycle(1);
    top->wen = 0;

    while (top->wbusy == 1)
        simcycle(1);
}
void print_turn(){
    return;
    for (size_t i = 0; i < CACHE_SIZE/512; i++)
    {
        printf("%d ",top->rootp->sim_cache__DOT__inst_cache__DOT__TURN.m_storage[i]);
    }
    printf("\n");
    
}
void store_in_cache() {
    uint64_t start_cycles = total_cycles;
    for (size_t i = 0; i < CACHE_SIZE; i+=8)
    {
        store(i,rand(),8);
    }
    printf("in cache store use %ld cycles\n",total_cycles-start_cycles);
}

void print_hit_info() {
    uint64_t hit_sum = hit[0] + hit[1];
    printf("\thit rate:%03.02f%% hit0:%ld hit1:%ld total:%ld \n",(float)hit_sum*100/total_access,hit[0],hit[1],total_access);
}
void load_in_cache(){
    hit[0] = hit[1] = total_access = 0;
    uint64_t start_cycles = total_cycles;
    for (size_t i = 0; i < CACHE_SIZE; i+=8)
    {
        uint64_t data = load(i,8);
        if(data != mem[i / sizeof(uint64_t)]) {
            printf("i: 0x%lx data:0x%016lx mem:0x%016lx simcycles:%d\n", i, data, mem[i / sizeof(uint64_t)], total_cycles);
        }
    }
    printf("in cache load use %ld cycles\n",total_cycles-start_cycles);
    // print_hit_info();
}

void rand_load(int n) {
    uint64_t start_cycles = total_cycles;
    hit[0] = hit[1] = total_access = 0;
    int temp = n;

    uint64_t random_space = (uint64_t)(CACHE_SIZE/0.1);
    while (temp--)
    {
        uint64_t addr = rand();
        addr %= random_space;
        addr %= MEM_SIZE;
        uint8_t size = rand();
        size %= 3;
        size =  1 << size;
        addr = addr-(addr%size);
        uint64_t data8;
        uint32_t data4;
        uint16_t data2;
        uint8_t data1;
        switch (size)
        {
        case 1:
            data1 = load(addr,1);
            if (data1 != ((uint8_t*)mem)[addr / sizeof(uint8_t)])
            {
                printf("i: 0x%lx data:0x%016lx mem:0x%016x simcycles:%d\n", addr, (uint64_t)data1, ((uint8_t*)mem)[addr / sizeof(uint8_t)], total_cycles);
                simcycle(5);
                break;
            }
            break;
        case 2:
            data2 = load(addr,2);
            if (data2 != ((uint16_t*)mem)[addr / sizeof(uint16_t)])
            {
                printf("i: 0x%lx data:0x%016lx mem:0x%016x simcycles:%d\n", addr, (uint64_t)data2, ((uint16_t*)mem)[addr / sizeof(uint16_t)], total_cycles);
                simcycle(5);
                break;
            }
            break;
        case 4:
            data4 = load(addr,4);
            if (data4 != ((uint32_t*)mem)[addr / sizeof(uint32_t)])
            {
                printf("i: 0x%lx data:0x%016lx mem:0x%016x simcycles:%d\n", addr, (uint64_t)data4, ((uint32_t*)mem)[addr / sizeof(uint32_t)], total_cycles);
                simcycle(5);
                break;
            }
            break;
        case 8:
            data8 = load(addr,8);
            if (data8 != ((uint64_t*)mem)[addr / sizeof(uint64_t)])
            {
                printf("i: 0x%lx data:0x%016lx mem:0x%016lx simcycles:%d\n", addr, (uint64_t)data8, ((uint64_t*)mem)[addr / sizeof(uint64_t)], total_cycles);
                simcycle(5);
                break;
            }
            break;
        default:
        assert(0);
            break;
        }
        
    }
    printf("random load %d times, use %ld cycles\n",n,total_cycles-start_cycles);
    print_hit_info();
    
}


int main(int argc, char **argv, char **env)
{
    top = new Vsim_cache;
#if TRACE
    tfp = new VerilatedVcdC; // 初始化VCD对象指针
    Verilated::traceEverOn(true); // 打开追踪功能
    top->trace(tfp, 0);
    tfp->open("wave_sim_cache.vcd"); // 打开vcd
#endif

    srand(0);
    reset();
    init_mem();
    printf("sim start\n");
    top->direct = 0;
    load_in_cache();
    load_in_cache();
    
    top->direct = 1;
    printf("mtime:%d\n",load(0x0200BFF8,8));
    store(0x0200BFF8,10000,8);
    printf("mtime:%d\n",load(0x0200BFF8,8));
    printf("mtimecmp:%d\n",load(0x02004000,8));
    store(0x02004000,3000,8);
    printf("mtimecmp:%d\n",load(0x02004000,8));
    top->direct = 0;

    top->direct = 1;
    store_in_cache();
    top->direct = 0;
    
    top->invalid = 1;
    simcycle(1);
    top->invalid = 0;
    load_in_cache();
    load_in_cache();
    
    top->direct = 0;
    rand_load(32000);
    
    printf("sim stop\n");
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    return 0;
}
void dump_wave()
{
#if TRACE
    static uint32_t wave_cnt = 0;
    if (wave_cnt > WAVE_MAX_CNT)
        return;
    tfp->dump(wave_cnt++);
#endif
}
void quit(int t)
{
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    exit(t);
}
void simcycle(uint32_t n)
{
    total_cycles += n;

    while (n--)
    {
        top->clk = 1;
        top->eval();
        dump_wave();
        top->clk = 0;
        top->eval();
        dump_wave();
    }
    if (total_cycles > SIM_MAX_CYCLES)
        quit(-1);
}
void reset()
{
    top->clk = 0;
    top->rst = 1;

    top->eval();
    simcycle(10);
    top->rst = 0;
}
#ifdef __cplusplus
extern "C"
{
#endif

    // DPI IMPORTS
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/Cache/axi_mem.v:1:33
    long long mem_read(long long raddr, svBit is_icache)
    {
        return mem[raddr / sizeof(uint64_t)];
    }
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/Cache/axi_mem.v:2:30
    void mem_write(long long waddr, long long wdata, char wmask)
    {
        uint64_t old = mem[waddr / sizeof(uint64_t)];
        uint64_t temp = 0;
        for (int i = 0; i < sizeof(uint64_t); i++)
        {
            if (BITS(wmask, i, i))
            {
                temp |= BITS(wdata, i * 8 + 7, i * 8) << i * 8;
            }
            else
            {
                temp |= BITS(old, i * 8 + 7, i * 8) << i * 8;
            }
        }
        mem[waddr / sizeof(uint64_t)] = temp;
        // printf("mem_write: addr: 0x%016lx data:0x%016lx size:0x%02x mem:0x%016lx to 0x%016lx\n",waddr,wdata,(uint8_t)wmask,old,temp);
    }

    void other_inst(svBit ecall, svBit fence, svBit ebreak) { return; }
    long long pmem_read(long long raddr, char rsize, char is_icache) { return 0; }
    void pmem_write(long long waddr, long long wdata, char wmask) { return; }
    void raise_hard_intr() {}
    void submit_inst(long long pc, int inst, long long next_pc) {}
    void difftest_memory_access(long long addr) {}
    
    long long axi4_read(int raddr, char rsize)
    {
        // printf("axi4_read: raddr:0x%08x rsize:%d\n",raddr,rsize);
        uint8_t *p8 = (uint8_t *)((uintptr_t)raddr+(uintptr_t)mem);
        uint16_t *p16 = (uint16_t *)((uintptr_t)raddr+(uintptr_t)mem);
        uint32_t *p32 = (uint32_t *)((uintptr_t)raddr+(uintptr_t)mem);
        uint64_t *p64 = (uint64_t *)((uintptr_t)raddr+(uintptr_t)mem);
        switch (rsize)
        {
        case 0: return *(p8);
        case 1: return *(p16);
        case 2: return *(p32);
        case 3: return *(p64);
        default: assert(0);
        }
    }
    
    void axi4_write(int waddr, long long wdata, char wsize)
    {
        // printf("axi4_write: waddr:0x%08x wdata:0x%016lx wsize:%d\n",waddr,wdata,wsize);
        uint8_t *p8 = (uint8_t *)((uintptr_t)waddr+(uintptr_t)mem);
        uint16_t *p16 = (uint16_t *)((uintptr_t)waddr+(uintptr_t)mem);
        uint32_t *p32 = (uint32_t *)((uintptr_t)waddr+(uintptr_t)mem);
        uint64_t *p64 = (uint64_t *)((uintptr_t)waddr+(uintptr_t)mem);
        switch (wsize)
        {
        case 0: *p8 = wdata; break;
        case 1: *p16 = wdata; break;
        case 2: *p32 = wdata; break;
        case 3: *p64 = wdata; break;
        default: assert(0);
        }
    }
#ifdef __cplusplus
}
void init_mem()
{
    for (size_t i = 0; i < MEM_SIZE / sizeof(uint64_t); i++)
    {
        mem[i] = rand();
    }
}
#endif